Introduction

Leaflet is one of the most popular open-source JavaScript libraries for interactive maps. It’s used by websites ranging from The New York Times and The Washington Post to GitHub and Flickr, as well as GIS specialists like OpenStreetMap, Mapbox, and CartoDB.

This R package makes it easy to integrate and control Leaflet maps in R.

library(tidyverse)     # for data cleaning and plotting
library(gplots)        # for col2hex() function
library(RColorBrewer)  # for color palettes
library(sf)            # for working with spatial data
library(leaflet)       # for highly customizable mapping
library(carData)       # for Minneapolis police stops data
library(tidytuesdayR)  # for bigfoot data 
library(ggthemes)      # for more themes (including theme_map())
library(htmltools)
theme_set(theme_minimal())

Features

Interactive panning/zooming Compose maps using arbitrary combinations of: ·Map tiles ·Markers ·Polygons ·Lines ·Popups ·GeoJSON Create maps right from the R console or RStudio Embed maps in knitr/R Markdown documents and Shiny apps Easily render spatial objects from the sp or sf packages, or data frames with latitude/longitude columns Use map bounds and mouse events to drive Shiny logic Display maps in non spherical mercator projections *Augment map features using chosen plugins from leaflet plugins repository

Basemaps

We are gonna start from the bottokm layer up. The first thing to look it is the basemaps. This means, what will be behind your points or shapes. There are many options and they are super easy to load in. We can start off with the basic basic map, simpple blue ocean and white land, if you zoom in there will be more detials

leaflet() %>% 
  addTiles()

We can also start the map zoomed in on a specific area using latitude and longitude and the setView() function

Here is Saint Paul MN, now look up your hometown and practice puting in the coordinates.

leaflet() %>% 
  addTiles() %>% 
  setView(lng = -93.093124, lat = 44.949642, zoom = 12)
hometown <- leaflet() %>% 
  setView(lng = -93.093124, lat = 44.949642, zoom = 12) %>% 
    addTiles()

There are many other basemaps you can use in leaflet! To get different basemaps use the function addProviderTiles(), you will need to know the name of the basemap Here are some examples:

hometown %>% 
    addProviderTiles(providers$CartoDB.Positron)
hometown %>% 
    addProviderTiles(providers$Stamen.Watercolor)
hometown %>% 
    addProviderTiles(providers$CartoDB.DarkMatterNoLabels)

To get the full list of basemaps available through this function click here: http://leaflet-extras.github.io/leaflet-providers/preview/index.html

Let say you really like the water color basemap but there arent any labels, you can layer base maps. Here I have layed the water color with the light grey basemap that had labels. As long as you set the one on top to have a lower opacity you can see both!

hometown %>% addProviderTiles(providers$Stamen.Watercolor) %>%
  addProviderTiles(providers$CartoDB.Positron,
    options = providerTileOptions(opacity = 0.5)) 

Markers

Now we have our basemap figured out whe can add markers. To do that we will needed data and the observation should have a latitude and longitdue variable to put them on the map.

tuesdata <- tidytuesdayR::tt_load('2022-09-13') #Loading in data from a tidy tuesday that has geographical points
## 
##  Downloading file 1 of 1: `bigfoot.csv`
bigfoot <- tuesdata$bigfoot
leaflet(data = bigfoot) %>% addTiles() %>%
  addMarkers(~longitude, ~latitude)

If R is running slowly now its probably becuase there are so many data points. It is best to filter out observations that you need before making a map.

bigfootsub <- bigfoot %>% 
  filter(season == "Summer")
leaflet(data = bigfootsub) %>% addTiles() %>%
  addMarkers(~longitude, ~latitude)

With less points the software runs far smoother. Now we can add some fun things!

Plain Markers and Popups

Not only can you add labels to these points but with leaflet you can add popups

leaflet(data = bigfootsub) %>% 
  addProviderTiles(providers$CartoDB.Positron) %>% #Changing basemap to something more neutral 
  addMarkers(~longitude, ~latitude,label = ~date, popup = ~location_details) # label means what will appear when you hover over the point and popup means what will appear when you click on a point

Awesome Markers

Awesome markers allow you to change the color of the marker dependent on a variable

# After choosing temperature_high to be my variable I am visualizing I am going to filter out any observations that do not have a value for temperature_high
bftemp <- bigfootsub %>% 
  filter(temperature_high != "NA")
bftemp
#Then we will need to assign values of temperature_high  to colors creating a getColor function 

getColor <- function(bftemp) {
  sapply(bftemp$temperature_high, function(temperature_high){
  if(temperature_high <= 68) { 
    "blue"
  } else if(temperature_high >= 69) {
    "red"
  } else {
    "green"
  } })
}

icons <- awesomeIcons(
  icon = 'ios-close',
  iconColor = 'pink', #Controls color of middle x 
  library = 'ion',
  markerColor = getColor(bftemp) #Calls the function 
)

leaflet(bftemp) %>% 
  addProviderTiles(providers$CartoDB.Positron) %>% 
  addAwesomeMarkers(~longitude, ~latitude,label = ~date, popup = ~location_details, icon = icons) #make sure to set the icons

Color

Instead of using color as in the ggplot, we will use color palette for leaflet. In the palette, you call 1) the colors you want to use and 2) optionally, the range of inputs (i.e. domain) that are expected. The color function returns a palette function that can be passed a vector of input values, and it’ll return a vector of colors in #RRGGBB(AA) format.There are currently three color functions for dealing with continuous input: colorNumeric, colorBin, and colorQuantile; and one for categorical input, colorFactor.

# load continuous dataset
data_site <- 
  "https://www.macalester.edu/~dshuman1/data/112/2014-Q4-Trips-History-Data.rds" 
Trips <- readRDS(gzcon(url(data_site)))
Stations<-read_csv("http://www.macalester.edu/~dshuman1/data/112/DC-Stations.csv")

departSta <- Trips %>%
  left_join(Stations, by = c("sstation" = "name")) %>%
  group_by(lat, long) %>%
  summarise(EventsCount = n())

Create Color Palette

domain arguement is optional, it tells the color function the range of input values( the specific variable you want to include).If you use a palette function multiple times across different data, it’s important to provide a non-NULL value for domain so the scaling between data and colors is consistent.

# Call the color function (colorNumeric) to create a new palette function
pal <- colorNumeric(c("red", "green", "blue"), 1:10)
# Pass the palette function a data vector to get the corresponding colors
pal(c(1,6,9))
## [1] "#FF0000" "#52E74B" "#6854D8"
# create another color palette function with the range of inputs (i.e. domain) 
palDomain <- colorNumeric(
  palette = "Blues",
  domain = departSta$EventsCount)
# Show the corresponding colors
head(palDomain(departSta$EventsCount))
## [1] "#F5FAFE" "#EFF6FC" "#F2F8FD" "#F5F9FE" "#EEF5FC" "#EFF6FC"

Common parameters

#RColorBrewer 
palBre <- colorNumeric(
  palette = "RdYlBu",
  domain = departSta$EventsCount)

#viridis
palVir <- colorNumeric(
  palette = "magma",
  domain = departSta$EventsCount)

#RGB or Named the colors: palette(), c("#000000", "#0000FF", "#FFFFFF"), topo.colors(10) etc

#A function that receives a single value between 0 and 1 and returns a color: colorRamp(c("#000000", "#FFFFFF"), interpolate="spline") etc

Continuous data

#Continuous input, continuous colors (colorNumeric)
palConC <- colorNumeric(
  palette = "RdYlBu",
  domain = departSta$EventsCount)

#Continuous input, discrete colors (colorBin and colorQuantile)

# colorBin:slicing the input domain up by value(bin)
palBin<-colorBin("Blues", departSta$EventsCount, 5, pretty = FALSE)

#colorQuantile: slicing the input domain into subsets with equal numbers of observations (by quantile)
palQuan <- colorQuantile("Blues", departSta$EventsCount, n = 7)
# colorBin
leaflet(data = departSta) %>%
  addProviderTiles(providers$CartoDB.DarkMatter) %>%
  addProviderTiles(providers$Stamen.TonerLines,
                   options = providerTileOptions(opacity = 0.35)) %>%
  addProviderTiles(providers$Stamen.TonerLabels) %>%
    addCircles(lng = ~long,
             lat = ~lat,
            #stroke width in pixels
             weight = 10,
            #changes transparency, like alpha in ggplot
             opacity = 1,
             color = ~palBin(EventsCount))
# colorQuantile
leaflet(data = departSta) %>%
  addProviderTiles(providers$CartoDB.DarkMatter) %>%
  addProviderTiles(providers$Stamen.TonerLines,
                   options = providerTileOptions(opacity = 0.35)) %>%
  addProviderTiles(providers$Stamen.TonerLabels) %>%
    addCircles(lng = ~long,
             lat = ~lat,
            #stroke width in pixels
             weight = 10,
            #changes transparency, like alpha in ggplot
             opacity = 1,
             color = ~palQuan(EventsCount))

categorical data

For categorical data, you will use the colorFactor function. If you want to specify the input domain, you can either by passing a factor or character vector to domain, or by providing levels directly using the levels parameter (in which case the domain will be ignored).

#Domain
palFacD<-colorFactor(palette = "Blues", MplsStops$problem)
#Level
palFacL<-colorFactor(topo.colors(5),levels = MplsStops$problem)
leaflet(data = MplsStops) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addCircleMarkers(lng = ~long,
             lat = ~lat,
             weight = 1,
             opacity = 1,
             stroke = TRUE,
             color = ~palFacL(problem))

Lines and Shape

For creating a rectangle, the four vector arguments indicating its four angles are required.While the Polygons and Polylines are more flexible and can be inferred from the data object.

#rectangle
leaflet(data = departSta) %>%
  addProviderTiles(providers$CartoDB.DarkMatter) %>%
  addProviderTiles(providers$Stamen.TonerLines,
                   options = providerTileOptions(opacity = 0.35)) %>%
  addProviderTiles(providers$Stamen.TonerLabels) %>%
  addCircles(
    lng = ~ long,
    lat = ~ lat,
    #stroke width in pixels
    weight = 10,
    #changes transparency, like alpha in ggplot
    opacity = 1,
    color = ~ palQuan(EventsCount)
  ) %>%
  addRectangles(
    lng1 = -77.20250,
    lat1 =38.80111,
    lng2 =-76.93186,
    lat2 = 39.12351,
    fillColor = "transparent"
  )
#Polygons and Polylines
leaflet(data = departSta) %>%
  addProviderTiles(providers$CartoDB.DarkMatter) %>%
  addProviderTiles(providers$Stamen.TonerLines,
                   options = providerTileOptions(opacity = 0.35)) %>%
  addProviderTiles(providers$Stamen.TonerLabels) %>%
  addPolygons(
    lng = ~ long,
    lat = ~ lat,
    # set the opacity of the outline
    opacity = 1,
    # set the stroke width in pixels
    weight = 1,
    # set the fill opacity
    fillOpacity = 0.6
  )

Legend

addLegend() function is aware of the different types of palette functions, and will create an appropriate default rendering for each type.Thus, you do not need to edit it manully when changing the domain or other scales.

leaflet(data = MplsStops) %>%
  addProviderTiles(providers$CartoDB.Positron) %>%
  addCircleMarkers(lng = ~long,
             lat = ~lat,
             weight = 1,
             opacity = 1,
             stroke = TRUE,
             color = ~palFacL(problem)) %>% 
    addLegend(position = "bottomleft", 
            pal = palFacL,
            values = ~problem,
             title = "Type of Stops") 

Choropleth Map

states <- geojsonio::geojson_read("https://rstudio.github.io/leaflet/json/us-states.geojson", what = "sp")

bins <- c(0, 10, 20, 50, 100, 200, 500, 1000, Inf)
pal <- colorBin("YlOrRd", domain = states$density, bins = bins)

labels <- sprintf(
  "<strong>%s</strong><br/>%g people / mi<sup>2</sup>",
  states$name, states$density
) %>% lapply(htmltools::HTML)

leaflet(states) %>%
  setView(-96, 37.8, 4) %>%
  addProviderTiles("MapBox", options = providerTileOptions(
    id = "mapbox.light",
    accessToken = Sys.getenv('MAPBOX_ACCESS_TOKEN'))) %>% 
  addPolygons(
    fillColor = ~pal(density),
    weight = 2,
    opacity = 1,
    color = "white",
    dashArray = "3",
    fillOpacity = 0.7,
    # hightlight the polygon when curse over it
    highlightOptions = highlightOptions(
      weight = 5,
      color = "#666",
      dashArray = "",
      fillOpacity = 0.7,
      bringToFront = TRUE),
    # add labels
    label = labels,
    labelOptions = labelOptions(
      style = list("font-weight" = "normal", padding = "3px 8px"),
      textsize = "15px",
      direction = "auto"))
LS0tDQp0aXRsZTogJ0xlYXJuaW5nIEd1aWRlIERyYWZ0Jw0KYXV0aG9yOiAiUGlwcGEsUml0YSxKZW5ueSINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6DQogICAga2VlcF9tZDogVFJVRQ0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDogVFJVRQ0KICAgIGRmX3ByaW50OiBwYWdlZA0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgZXJyb3I9VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSkNCmBgYA0KDQojIEludHJvZHVjdGlvbg0KTGVhZmxldCBpcyBvbmUgb2YgdGhlIG1vc3QgcG9wdWxhciBvcGVuLXNvdXJjZSBKYXZhU2NyaXB0IGxpYnJhcmllcyBmb3IgaW50ZXJhY3RpdmUgbWFwcy4gSXTigJlzIHVzZWQgYnkgd2Vic2l0ZXMgcmFuZ2luZyBmcm9tIFRoZSBOZXcgWW9yayBUaW1lcyBhbmQgVGhlIFdhc2hpbmd0b24gUG9zdCB0byBHaXRIdWIgYW5kIEZsaWNrciwgYXMgd2VsbCBhcyBHSVMgc3BlY2lhbGlzdHMgbGlrZSBPcGVuU3RyZWV0TWFwLCBNYXBib3gsIGFuZCBDYXJ0b0RCLg0KDQpUaGlzIFIgcGFja2FnZSBtYWtlcyBpdCBlYXN5IHRvIGludGVncmF0ZSBhbmQgY29udHJvbCBMZWFmbGV0IG1hcHMgaW4gUi4NCg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkgICAgICMgZm9yIGRhdGEgY2xlYW5pbmcgYW5kIHBsb3R0aW5nDQpsaWJyYXJ5KGdwbG90cykgICAgICAgICMgZm9yIGNvbDJoZXgoKSBmdW5jdGlvbg0KbGlicmFyeShSQ29sb3JCcmV3ZXIpICAjIGZvciBjb2xvciBwYWxldHRlcw0KbGlicmFyeShzZikgICAgICAgICAgICAjIGZvciB3b3JraW5nIHdpdGggc3BhdGlhbCBkYXRhDQpsaWJyYXJ5KGxlYWZsZXQpICAgICAgICMgZm9yIGhpZ2hseSBjdXN0b21pemFibGUgbWFwcGluZw0KbGlicmFyeShjYXJEYXRhKSAgICAgICAjIGZvciBNaW5uZWFwb2xpcyBwb2xpY2Ugc3RvcHMgZGF0YQ0KbGlicmFyeSh0aWR5dHVlc2RheVIpICAjIGZvciBiaWdmb290IGRhdGEgDQpsaWJyYXJ5KGdndGhlbWVzKSAgICAgICMgZm9yIG1vcmUgdGhlbWVzIChpbmNsdWRpbmcgdGhlbWVfbWFwKCkpDQpsaWJyYXJ5KGh0bWx0b29scykNCnRoZW1lX3NldCh0aGVtZV9taW5pbWFsKCkpDQpgYGANCg0KIyMgRmVhdHVyZXMNCkludGVyYWN0aXZlIHBhbm5pbmcvem9vbWluZw0KICAqQ29tcG9zZSBtYXBzIHVzaW5nIGFyYml0cmFyeSBjb21iaW5hdGlvbnMgb2Y6DQogICAgICDCt01hcCB0aWxlcw0KICAgICAgwrdNYXJrZXJzDQogICAgICDCt1BvbHlnb25zDQogICAgICDCt0xpbmVzDQogICAgICDCt1BvcHVwcw0KICAgICAgwrdHZW9KU09ODQogICpDcmVhdGUgbWFwcyByaWdodCBmcm9tIHRoZSBSIGNvbnNvbGUgb3IgUlN0dWRpbw0KICAqRW1iZWQgbWFwcyBpbiBrbml0ci9SIE1hcmtkb3duIGRvY3VtZW50cyBhbmQgU2hpbnkgYXBwcw0KICAqRWFzaWx5IHJlbmRlciBzcGF0aWFsIG9iamVjdHMgZnJvbSB0aGUgc3Agb3Igc2YgcGFja2FnZXMsIG9yIGRhdGEgZnJhbWVzIHdpdGggbGF0aXR1ZGUvbG9uZ2l0dWRlIGNvbHVtbnMNCiAgKlVzZSBtYXAgYm91bmRzIGFuZCBtb3VzZSBldmVudHMgdG8gZHJpdmUgU2hpbnkgbG9naWMNCiAgKkRpc3BsYXkgbWFwcyBpbiBub24gc3BoZXJpY2FsIG1lcmNhdG9yIHByb2plY3Rpb25zDQogICpBdWdtZW50IG1hcCBmZWF0dXJlcyB1c2luZyBjaG9zZW4gcGx1Z2lucyBmcm9tIGxlYWZsZXQgcGx1Z2lucyByZXBvc2l0b3J5DQoNCg0KIyBCYXNlbWFwcw0KDQpXZSBhcmUgZ29ubmEgc3RhcnQgZnJvbSB0aGUgYm90dG9rbSBsYXllciB1cC4gVGhlIGZpcnN0IHRoaW5nIHRvIGxvb2sgaXQgaXMgdGhlIGJhc2VtYXBzLiBUaGlzIG1lYW5zLCB3aGF0IHdpbGwgYmUgYmVoaW5kIHlvdXIgcG9pbnRzIG9yIHNoYXBlcy4gVGhlcmUgYXJlIG1hbnkgb3B0aW9ucyBhbmQgdGhleSBhcmUgc3VwZXIgZWFzeSB0byBsb2FkIGluLiANCldlIGNhbiBzdGFydCBvZmYgd2l0aCB0aGUgYmFzaWMgYmFzaWMgbWFwLCBzaW1wcGxlIGJsdWUgb2NlYW4gYW5kIHdoaXRlIGxhbmQsIGlmIHlvdSB6b29tIGluIHRoZXJlIHdpbGwgYmUgbW9yZSBkZXRpYWxzDQoNCmBgYHtyfQ0KbGVhZmxldCgpICU+JSANCiAgYWRkVGlsZXMoKQ0KYGBgDQoNCldlIGNhbiBhbHNvIHN0YXJ0IHRoZSBtYXAgem9vbWVkIGluIG9uIGEgc3BlY2lmaWMgYXJlYSB1c2luZyBsYXRpdHVkZSBhbmQgbG9uZ2l0dWRlIGFuZCB0aGUgc2V0VmlldygpIGZ1bmN0aW9uIA0KDQpIZXJlIGlzIFNhaW50IFBhdWwgTU4sIG5vdyBsb29rIHVwIHlvdXIgaG9tZXRvd24gYW5kIHByYWN0aWNlIHB1dGluZyBpbiB0aGUgY29vcmRpbmF0ZXMuIA0KDQpgYGB7cn0NCmxlYWZsZXQoKSAlPiUgDQogIGFkZFRpbGVzKCkgJT4lIA0KICBzZXRWaWV3KGxuZyA9IC05My4wOTMxMjQsIGxhdCA9IDQ0Ljk0OTY0Miwgem9vbSA9IDEyKQ0KICANCmBgYA0KDQpgYGB7cn0NCmhvbWV0b3duIDwtIGxlYWZsZXQoKSAlPiUgDQogIHNldFZpZXcobG5nID0gLTkzLjA5MzEyNCwgbGF0ID0gNDQuOTQ5NjQyLCB6b29tID0gMTIpICU+JSANCiAgICBhZGRUaWxlcygpDQogIA0KYGBgDQoNClRoZXJlIGFyZSBtYW55IG90aGVyIGJhc2VtYXBzIHlvdSBjYW4gdXNlIGluIGxlYWZsZXQhIA0KVG8gZ2V0IGRpZmZlcmVudCBiYXNlbWFwcyB1c2UgdGhlIGZ1bmN0aW9uIGFkZFByb3ZpZGVyVGlsZXMoKSwgeW91IHdpbGwgbmVlZCB0byBrbm93IHRoZSBuYW1lIG9mIHRoZSBiYXNlbWFwIA0KSGVyZSBhcmUgc29tZSBleGFtcGxlczogDQoNCmBgYHtyfQ0KaG9tZXRvd24gJT4lIA0KICAgIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pDQpgYGANCg0KYGBge3J9DQpob21ldG93biAlPiUgDQogICAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkU3RhbWVuLldhdGVyY29sb3IpDQpgYGANCg0KYGBge3J9DQpob21ldG93biAlPiUgDQogICAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5EYXJrTWF0dGVyTm9MYWJlbHMpDQpgYGANCg0KVG8gZ2V0IHRoZSBmdWxsIGxpc3Qgb2YgYmFzZW1hcHMgYXZhaWxhYmxlIHRocm91Z2ggdGhpcyBmdW5jdGlvbiBjbGljayBoZXJlOiBodHRwOi8vbGVhZmxldC1leHRyYXMuZ2l0aHViLmlvL2xlYWZsZXQtcHJvdmlkZXJzL3ByZXZpZXcvaW5kZXguaHRtbCANCg0KTGV0IHNheSB5b3UgcmVhbGx5IGxpa2UgdGhlIHdhdGVyIGNvbG9yIGJhc2VtYXAgYnV0IHRoZXJlIGFyZW50IGFueSBsYWJlbHMsIHlvdSBjYW4gbGF5ZXIgYmFzZSBtYXBzLiBIZXJlIEkgaGF2ZSBsYXllZCB0aGUgd2F0ZXIgY29sb3Igd2l0aCB0aGUgbGlnaHQgZ3JleSBiYXNlbWFwIHRoYXQgaGFkIGxhYmVscy4gQXMgbG9uZyBhcyB5b3Ugc2V0IHRoZSBvbmUgb24gdG9wIHRvIGhhdmUgYSBsb3dlciBvcGFjaXR5IHlvdSBjYW4gc2VlIGJvdGghICANCg0KYGBge3J9DQpob21ldG93biAlPiUgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkU3RhbWVuLldhdGVyY29sb3IpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLlBvc2l0cm9uLA0KICAgIG9wdGlvbnMgPSBwcm92aWRlclRpbGVPcHRpb25zKG9wYWNpdHkgPSAwLjUpKSANCiANCmBgYA0KDQojIE1hcmtlcnMgDQoNCk5vdyB3ZSBoYXZlIG91ciBiYXNlbWFwIGZpZ3VyZWQgb3V0IHdoZSBjYW4gYWRkIG1hcmtlcnMuIFRvIGRvIHRoYXQgd2Ugd2lsbCBuZWVkZWQgZGF0YSBhbmQgdGhlIG9ic2VydmF0aW9uIHNob3VsZCBoYXZlIGEgbGF0aXR1ZGUgYW5kIGxvbmdpdGR1ZSB2YXJpYWJsZSB0byBwdXQgdGhlbSBvbiB0aGUgbWFwLiANCg0KYGBge3J9DQp0dWVzZGF0YSA8LSB0aWR5dHVlc2RheVI6OnR0X2xvYWQoJzIwMjItMDktMTMnKSAjTG9hZGluZyBpbiBkYXRhIGZyb20gYSB0aWR5IHR1ZXNkYXkgdGhhdCBoYXMgZ2VvZ3JhcGhpY2FsIHBvaW50cw0KYmlnZm9vdCA8LSB0dWVzZGF0YSRiaWdmb290DQpgYGANCg0KYGBge3J9DQpsZWFmbGV0KGRhdGEgPSBiaWdmb290KSAlPiUgYWRkVGlsZXMoKSAlPiUNCiAgYWRkTWFya2Vycyh+bG9uZ2l0dWRlLCB+bGF0aXR1ZGUpDQpgYGANCg0KSWYgUiBpcyBydW5uaW5nIHNsb3dseSBub3cgaXRzIHByb2JhYmx5IGJlY3Vhc2UgdGhlcmUgYXJlIHNvIG1hbnkgZGF0YSBwb2ludHMuIEl0IGlzIGJlc3QgdG8gZmlsdGVyIG91dCBvYnNlcnZhdGlvbnMgdGhhdCB5b3UgbmVlZCBiZWZvcmUgbWFraW5nIGEgbWFwLiANCg0KYGBge3J9DQpiaWdmb290c3ViIDwtIGJpZ2Zvb3QgJT4lIA0KICBmaWx0ZXIoc2Vhc29uID09ICJTdW1tZXIiKQ0KYGBgDQoNCmBgYHtyfQ0KbGVhZmxldChkYXRhID0gYmlnZm9vdHN1YikgJT4lIGFkZFRpbGVzKCkgJT4lDQogIGFkZE1hcmtlcnMofmxvbmdpdHVkZSwgfmxhdGl0dWRlKQ0KYGBgDQpXaXRoIGxlc3MgcG9pbnRzIHRoZSBzb2Z0d2FyZSBydW5zIGZhciBzbW9vdGhlci4gTm93IHdlIGNhbiBhZGQgc29tZSBmdW4gdGhpbmdzISANCg0KIyBQbGFpbiBNYXJrZXJzIGFuZCBQb3B1cHMNCk5vdCBvbmx5IGNhbiB5b3UgYWRkIGxhYmVscyB0byB0aGVzZSBwb2ludHMgYnV0IHdpdGggbGVhZmxldCB5b3UgY2FuIGFkZCBwb3B1cHMgDQpgYGB7cn0NCmxlYWZsZXQoZGF0YSA9IGJpZ2Zvb3RzdWIpICU+JSANCiAgYWRkUHJvdmlkZXJUaWxlcyhwcm92aWRlcnMkQ2FydG9EQi5Qb3NpdHJvbikgJT4lICNDaGFuZ2luZyBiYXNlbWFwIHRvIHNvbWV0aGluZyBtb3JlIG5ldXRyYWwgDQogIGFkZE1hcmtlcnMofmxvbmdpdHVkZSwgfmxhdGl0dWRlLGxhYmVsID0gfmRhdGUsIHBvcHVwID0gfmxvY2F0aW9uX2RldGFpbHMpICMgbGFiZWwgbWVhbnMgd2hhdCB3aWxsIGFwcGVhciB3aGVuIHlvdSBob3ZlciBvdmVyIHRoZSBwb2ludCBhbmQgcG9wdXAgbWVhbnMgd2hhdCB3aWxsIGFwcGVhciB3aGVuIHlvdSBjbGljayBvbiBhIHBvaW50DQpgYGANCg0KIyBBd2Vzb21lIE1hcmtlcnMNCkF3ZXNvbWUgbWFya2VycyBhbGxvdyB5b3UgdG8gY2hhbmdlIHRoZSBjb2xvciBvZiB0aGUgbWFya2VyIGRlcGVuZGVudCBvbiBhIHZhcmlhYmxlIA0KYGBge3J9DQojIEFmdGVyIGNob29zaW5nIHRlbXBlcmF0dXJlX2hpZ2ggdG8gYmUgbXkgdmFyaWFibGUgSSBhbSB2aXN1YWxpemluZyBJIGFtIGdvaW5nIHRvIGZpbHRlciBvdXQgYW55IG9ic2VydmF0aW9ucyB0aGF0IGRvIG5vdCBoYXZlIGEgdmFsdWUgZm9yIHRlbXBlcmF0dXJlX2hpZ2gNCmJmdGVtcCA8LSBiaWdmb290c3ViICU+JSANCiAgZmlsdGVyKHRlbXBlcmF0dXJlX2hpZ2ggIT0gIk5BIikNCmJmdGVtcA0KYGBgDQoNCg0KYGBge3J9DQoNCiNUaGVuIHdlIHdpbGwgbmVlZCB0byBhc3NpZ24gdmFsdWVzIG9mIHRlbXBlcmF0dXJlX2hpZ2ggIHRvIGNvbG9ycyBjcmVhdGluZyBhIGdldENvbG9yIGZ1bmN0aW9uIA0KDQpnZXRDb2xvciA8LSBmdW5jdGlvbihiZnRlbXApIHsNCiAgc2FwcGx5KGJmdGVtcCR0ZW1wZXJhdHVyZV9oaWdoLCBmdW5jdGlvbih0ZW1wZXJhdHVyZV9oaWdoKXsNCiAgaWYodGVtcGVyYXR1cmVfaGlnaCA8PSA2OCkgeyANCiAgICAiYmx1ZSINCiAgfSBlbHNlIGlmKHRlbXBlcmF0dXJlX2hpZ2ggPj0gNjkpIHsNCiAgICAicmVkIg0KICB9IGVsc2Ugew0KICAgICJncmVlbiINCiAgfSB9KQ0KfQ0KDQppY29ucyA8LSBhd2Vzb21lSWNvbnMoDQogIGljb24gPSAnaW9zLWNsb3NlJywNCiAgaWNvbkNvbG9yID0gJ3BpbmsnLCAjQ29udHJvbHMgY29sb3Igb2YgbWlkZGxlIHggDQogIGxpYnJhcnkgPSAnaW9uJywNCiAgbWFya2VyQ29sb3IgPSBnZXRDb2xvcihiZnRlbXApICNDYWxscyB0aGUgZnVuY3Rpb24gDQopDQoNCmxlYWZsZXQoYmZ0ZW1wKSAlPiUgDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JSANCiAgYWRkQXdlc29tZU1hcmtlcnMofmxvbmdpdHVkZSwgfmxhdGl0dWRlLGxhYmVsID0gfmRhdGUsIHBvcHVwID0gfmxvY2F0aW9uX2RldGFpbHMsIGljb24gPSBpY29ucykgI21ha2Ugc3VyZSB0byBzZXQgdGhlIGljb25zDQpgYGANCiAgDQojIENvbG9yDQoNCj4gSW5zdGVhZCBvZiB1c2luZyBjb2xvciBhcyBpbiB0aGUgZ2dwbG90LCB3ZSB3aWxsIHVzZSBjb2xvciBwYWxldHRlIGZvciBsZWFmbGV0LiBJbiB0aGUgcGFsZXR0ZSwgeW91IGNhbGwgMSkgdGhlIGNvbG9ycyB5b3Ugd2FudCB0byB1c2UgYW5kIDIpIG9wdGlvbmFsbHksIHRoZSByYW5nZSBvZiBpbnB1dHMgKGkuZS4gZG9tYWluKSB0aGF0IGFyZSBleHBlY3RlZC4gVGhlIGNvbG9yIGZ1bmN0aW9uIHJldHVybnMgYSBwYWxldHRlIGZ1bmN0aW9uIHRoYXQgY2FuIGJlIHBhc3NlZCBhIHZlY3RvciBvZiBpbnB1dCB2YWx1ZXMsIGFuZCBpdOKAmWxsIHJldHVybiBhIHZlY3RvciBvZiBjb2xvcnMgaW4gI1JSR0dCQihBQSkgZm9ybWF0LlRoZXJlIGFyZSBjdXJyZW50bHkgdGhyZWUgY29sb3IgZnVuY3Rpb25zIGZvciBkZWFsaW5nIHdpdGggY29udGludW91cyBpbnB1dDogY29sb3JOdW1lcmljLCBjb2xvckJpbiwgYW5kIGNvbG9yUXVhbnRpbGU7IGFuZCBvbmUgZm9yIGNhdGVnb3JpY2FsIGlucHV0LCBjb2xvckZhY3Rvci4NCg0KYGBge3J9DQojIGxvYWQgY29udGludW91cyBkYXRhc2V0DQpkYXRhX3NpdGUgPC0gDQogICJodHRwczovL3d3dy5tYWNhbGVzdGVyLmVkdS9+ZHNodW1hbjEvZGF0YS8xMTIvMjAxNC1RNC1Ucmlwcy1IaXN0b3J5LURhdGEucmRzIiANClRyaXBzIDwtIHJlYWRSRFMoZ3pjb24odXJsKGRhdGFfc2l0ZSkpKQ0KU3RhdGlvbnM8LXJlYWRfY3N2KCJodHRwOi8vd3d3Lm1hY2FsZXN0ZXIuZWR1L35kc2h1bWFuMS9kYXRhLzExMi9EQy1TdGF0aW9ucy5jc3YiKQ0KDQpkZXBhcnRTdGEgPC0gVHJpcHMgJT4lDQogIGxlZnRfam9pbihTdGF0aW9ucywgYnkgPSBjKCJzc3RhdGlvbiIgPSAibmFtZSIpKSAlPiUNCiAgZ3JvdXBfYnkobGF0LCBsb25nKSAlPiUNCiAgc3VtbWFyaXNlKEV2ZW50c0NvdW50ID0gbigpKQ0KYGBgDQoNCiMjIENyZWF0ZSBDb2xvciBQYWxldHRlDQoNCj4gZG9tYWluIGFyZ3VlbWVudCBpcyBvcHRpb25hbCwgaXQgdGVsbHMgdGhlIGNvbG9yIGZ1bmN0aW9uIHRoZSByYW5nZSBvZiBpbnB1dCB2YWx1ZXMoIHRoZSBzcGVjaWZpYyB2YXJpYWJsZSB5b3Ugd2FudCB0byBpbmNsdWRlKS5JZiB5b3UgdXNlIGEgcGFsZXR0ZSBmdW5jdGlvbiBtdWx0aXBsZSB0aW1lcyBhY3Jvc3MgZGlmZmVyZW50IGRhdGEsIGl04oCZcyBpbXBvcnRhbnQgdG8gcHJvdmlkZSBhIG5vbi1OVUxMIHZhbHVlIGZvciBkb21haW4gc28gdGhlIHNjYWxpbmcgYmV0d2VlbiBkYXRhIGFuZCBjb2xvcnMgaXMgY29uc2lzdGVudC4NCg0KYGBge3J9DQojIENhbGwgdGhlIGNvbG9yIGZ1bmN0aW9uIChjb2xvck51bWVyaWMpIHRvIGNyZWF0ZSBhIG5ldyBwYWxldHRlIGZ1bmN0aW9uDQpwYWwgPC0gY29sb3JOdW1lcmljKGMoInJlZCIsICJncmVlbiIsICJibHVlIiksIDE6MTApDQojIFBhc3MgdGhlIHBhbGV0dGUgZnVuY3Rpb24gYSBkYXRhIHZlY3RvciB0byBnZXQgdGhlIGNvcnJlc3BvbmRpbmcgY29sb3JzDQpwYWwoYygxLDYsOSkpDQojIGNyZWF0ZSBhbm90aGVyIGNvbG9yIHBhbGV0dGUgZnVuY3Rpb24gd2l0aCB0aGUgcmFuZ2Ugb2YgaW5wdXRzIChpLmUuIGRvbWFpbikgDQpwYWxEb21haW4gPC0gY29sb3JOdW1lcmljKA0KICBwYWxldHRlID0gIkJsdWVzIiwNCiAgZG9tYWluID0gZGVwYXJ0U3RhJEV2ZW50c0NvdW50KQ0KIyBTaG93IHRoZSBjb3JyZXNwb25kaW5nIGNvbG9ycw0KaGVhZChwYWxEb21haW4oZGVwYXJ0U3RhJEV2ZW50c0NvdW50KSkNCmBgYA0KDQojIyBDb21tb24gcGFyYW1ldGVycw0KDQpgYGB7cn0NCiNSQ29sb3JCcmV3ZXIgDQpwYWxCcmUgPC0gY29sb3JOdW1lcmljKA0KICBwYWxldHRlID0gIlJkWWxCdSIsDQogIGRvbWFpbiA9IGRlcGFydFN0YSRFdmVudHNDb3VudCkNCg0KI3ZpcmlkaXMNCnBhbFZpciA8LSBjb2xvck51bWVyaWMoDQogIHBhbGV0dGUgPSAibWFnbWEiLA0KICBkb21haW4gPSBkZXBhcnRTdGEkRXZlbnRzQ291bnQpDQoNCiNSR0Igb3IgTmFtZWQgdGhlIGNvbG9yczogcGFsZXR0ZSgpLCBjKCIjMDAwMDAwIiwgIiMwMDAwRkYiLCAiI0ZGRkZGRiIpLCB0b3BvLmNvbG9ycygxMCkgZXRjDQoNCiNBIGZ1bmN0aW9uIHRoYXQgcmVjZWl2ZXMgYSBzaW5nbGUgdmFsdWUgYmV0d2VlbiAwIGFuZCAxIGFuZCByZXR1cm5zIGEgY29sb3I6IGNvbG9yUmFtcChjKCIjMDAwMDAwIiwgIiNGRkZGRkYiKSwgaW50ZXJwb2xhdGU9InNwbGluZSIpIGV0Yw0KYGBgDQoNCiMjIENvbnRpbnVvdXMgZGF0YQ0KDQpgYGB7cn0NCiNDb250aW51b3VzIGlucHV0LCBjb250aW51b3VzIGNvbG9ycyAoY29sb3JOdW1lcmljKQ0KcGFsQ29uQyA8LSBjb2xvck51bWVyaWMoDQogIHBhbGV0dGUgPSAiUmRZbEJ1IiwNCiAgZG9tYWluID0gZGVwYXJ0U3RhJEV2ZW50c0NvdW50KQ0KDQojQ29udGludW91cyBpbnB1dCwgZGlzY3JldGUgY29sb3JzIChjb2xvckJpbiBhbmQgY29sb3JRdWFudGlsZSkNCg0KIyBjb2xvckJpbjpzbGljaW5nIHRoZSBpbnB1dCBkb21haW4gdXAgYnkgdmFsdWUoYmluKQ0KcGFsQmluPC1jb2xvckJpbigiQmx1ZXMiLCBkZXBhcnRTdGEkRXZlbnRzQ291bnQsIDUsIHByZXR0eSA9IEZBTFNFKQ0KDQojY29sb3JRdWFudGlsZTogc2xpY2luZyB0aGUgaW5wdXQgZG9tYWluIGludG8gc3Vic2V0cyB3aXRoIGVxdWFsIG51bWJlcnMgb2Ygb2JzZXJ2YXRpb25zIChieSBxdWFudGlsZSkNCnBhbFF1YW4gPC0gY29sb3JRdWFudGlsZSgiQmx1ZXMiLCBkZXBhcnRTdGEkRXZlbnRzQ291bnQsIG4gPSA3KQ0KYGBgDQoNCg0KYGBge3IgZXhhbXBsZX0NCiMgY29sb3JCaW4NCmxlYWZsZXQoZGF0YSA9IGRlcGFydFN0YSkgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuRGFya01hdHRlcikgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJFN0YW1lbi5Ub25lckxpbmVzLA0KICAgICAgICAgICAgICAgICAgIG9wdGlvbnMgPSBwcm92aWRlclRpbGVPcHRpb25zKG9wYWNpdHkgPSAwLjM1KSkgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJFN0YW1lbi5Ub25lckxhYmVscykgJT4lDQogICAgYWRkQ2lyY2xlcyhsbmcgPSB+bG9uZywNCiAgICAgICAgICAgICBsYXQgPSB+bGF0LA0KICAgICAgICAgICAgI3N0cm9rZSB3aWR0aCBpbiBwaXhlbHMNCiAgICAgICAgICAgICB3ZWlnaHQgPSAxMCwNCiAgICAgICAgICAgICNjaGFuZ2VzIHRyYW5zcGFyZW5jeSwgbGlrZSBhbHBoYSBpbiBnZ3Bsb3QNCiAgICAgICAgICAgICBvcGFjaXR5ID0gMSwNCiAgICAgICAgICAgICBjb2xvciA9IH5wYWxCaW4oRXZlbnRzQ291bnQpKQ0KIyBjb2xvclF1YW50aWxlDQpsZWFmbGV0KGRhdGEgPSBkZXBhcnRTdGEpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLkRhcmtNYXR0ZXIpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRTdGFtZW4uVG9uZXJMaW5lcywNCiAgICAgICAgICAgICAgICAgICBvcHRpb25zID0gcHJvdmlkZXJUaWxlT3B0aW9ucyhvcGFjaXR5ID0gMC4zNSkpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRTdGFtZW4uVG9uZXJMYWJlbHMpICU+JQ0KICAgIGFkZENpcmNsZXMobG5nID0gfmxvbmcsDQogICAgICAgICAgICAgbGF0ID0gfmxhdCwNCiAgICAgICAgICAgICNzdHJva2Ugd2lkdGggaW4gcGl4ZWxzDQogICAgICAgICAgICAgd2VpZ2h0ID0gMTAsDQogICAgICAgICAgICAjY2hhbmdlcyB0cmFuc3BhcmVuY3ksIGxpa2UgYWxwaGEgaW4gZ2dwbG90DQogICAgICAgICAgICAgb3BhY2l0eSA9IDEsDQogICAgICAgICAgICAgY29sb3IgPSB+cGFsUXVhbihFdmVudHNDb3VudCkpDQpgYGANCg0KIyMgY2F0ZWdvcmljYWwgZGF0YQ0KDQo+IEZvciBjYXRlZ29yaWNhbCBkYXRhLCB5b3Ugd2lsbCB1c2UgdGhlIGNvbG9yRmFjdG9yIGZ1bmN0aW9uLiBJZiB5b3Ugd2FudCB0byBzcGVjaWZ5IHRoZSBpbnB1dCBkb21haW4sIHlvdSBjYW4gZWl0aGVyIGJ5IHBhc3NpbmcgYSBmYWN0b3Igb3IgY2hhcmFjdGVyIHZlY3RvciB0byBkb21haW4sIG9yIGJ5IHByb3ZpZGluZyBsZXZlbHMgZGlyZWN0bHkgdXNpbmcgdGhlIGxldmVscyBwYXJhbWV0ZXIgKGluIHdoaWNoIGNhc2UgdGhlIGRvbWFpbiB3aWxsIGJlIGlnbm9yZWQpLg0KDQpgYGB7cn0NCiNEb21haW4NCnBhbEZhY0Q8LWNvbG9yRmFjdG9yKHBhbGV0dGUgPSAiQmx1ZXMiLCBNcGxzU3RvcHMkcHJvYmxlbSkNCiNMZXZlbA0KcGFsRmFjTDwtY29sb3JGYWN0b3IodG9wby5jb2xvcnMoNSksbGV2ZWxzID0gTXBsc1N0b3BzJHByb2JsZW0pDQpgYGANCg0KYGBge3IgY2F0ZWdvcmljYWwgZXhhbXBsZX0NCmxlYWZsZXQoZGF0YSA9IE1wbHNTdG9wcykgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQ0KICBhZGRDaXJjbGVNYXJrZXJzKGxuZyA9IH5sb25nLA0KICAgICAgICAgICAgIGxhdCA9IH5sYXQsDQogICAgICAgICAgICAgd2VpZ2h0ID0gMSwNCiAgICAgICAgICAgICBvcGFjaXR5ID0gMSwNCiAgICAgICAgICAgICBzdHJva2UgPSBUUlVFLA0KICAgICAgICAgICAgIGNvbG9yID0gfnBhbEZhY0wocHJvYmxlbSkpDQpgYGANCg0KIyBMaW5lcyBhbmQgU2hhcGUNCg0KPiBGb3IgY3JlYXRpbmcgYSByZWN0YW5nbGUsIHRoZSBmb3VyIHZlY3RvciBhcmd1bWVudHMgaW5kaWNhdGluZyBpdHMgZm91ciBhbmdsZXMgYXJlIHJlcXVpcmVkLldoaWxlIHRoZSBQb2x5Z29ucyBhbmQgUG9seWxpbmVzIGFyZSBtb3JlIGZsZXhpYmxlIGFuZCBjYW4gYmUgaW5mZXJyZWQgZnJvbSB0aGUgZGF0YSBvYmplY3QuDQoNCg0KYGBge3J9DQojcmVjdGFuZ2xlDQpsZWFmbGV0KGRhdGEgPSBkZXBhcnRTdGEpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLkRhcmtNYXR0ZXIpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRTdGFtZW4uVG9uZXJMaW5lcywNCiAgICAgICAgICAgICAgICAgICBvcHRpb25zID0gcHJvdmlkZXJUaWxlT3B0aW9ucyhvcGFjaXR5ID0gMC4zNSkpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRTdGFtZW4uVG9uZXJMYWJlbHMpICU+JQ0KICBhZGRDaXJjbGVzKA0KICAgIGxuZyA9IH4gbG9uZywNCiAgICBsYXQgPSB+IGxhdCwNCiAgICAjc3Ryb2tlIHdpZHRoIGluIHBpeGVscw0KICAgIHdlaWdodCA9IDEwLA0KICAgICNjaGFuZ2VzIHRyYW5zcGFyZW5jeSwgbGlrZSBhbHBoYSBpbiBnZ3Bsb3QNCiAgICBvcGFjaXR5ID0gMSwNCiAgICBjb2xvciA9IH4gcGFsUXVhbihFdmVudHNDb3VudCkNCiAgKSAlPiUNCiAgYWRkUmVjdGFuZ2xlcygNCiAgICBsbmcxID0gLTc3LjIwMjUwLA0KICAgIGxhdDEgPTM4LjgwMTExLA0KICAgIGxuZzIgPS03Ni45MzE4NiwNCiAgICBsYXQyID0gMzkuMTIzNTEsDQogICAgZmlsbENvbG9yID0gInRyYW5zcGFyZW50Ig0KICApDQoNCiNQb2x5Z29ucyBhbmQgUG9seWxpbmVzDQpsZWFmbGV0KGRhdGEgPSBkZXBhcnRTdGEpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRDYXJ0b0RCLkRhcmtNYXR0ZXIpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRTdGFtZW4uVG9uZXJMaW5lcywNCiAgICAgICAgICAgICAgICAgICBvcHRpb25zID0gcHJvdmlkZXJUaWxlT3B0aW9ucyhvcGFjaXR5ID0gMC4zNSkpICU+JQ0KICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRTdGFtZW4uVG9uZXJMYWJlbHMpICU+JQ0KICBhZGRQb2x5Z29ucygNCiAgICBsbmcgPSB+IGxvbmcsDQogICAgbGF0ID0gfiBsYXQsDQogICAgIyBzZXQgdGhlIG9wYWNpdHkgb2YgdGhlIG91dGxpbmUNCiAgICBvcGFjaXR5ID0gMSwNCiAgICAjIHNldCB0aGUgc3Ryb2tlIHdpZHRoIGluIHBpeGVscw0KICAgIHdlaWdodCA9IDEsDQogICAgIyBzZXQgdGhlIGZpbGwgb3BhY2l0eQ0KICAgIGZpbGxPcGFjaXR5ID0gMC42DQogICkNCmBgYA0KDQojIExlZ2VuZA0KDQo+IGFkZExlZ2VuZCgpIGZ1bmN0aW9uIGlzIGF3YXJlIG9mIHRoZSBkaWZmZXJlbnQgdHlwZXMgb2YgcGFsZXR0ZSBmdW5jdGlvbnMsIGFuZCB3aWxsIGNyZWF0ZSBhbiBhcHByb3ByaWF0ZSBkZWZhdWx0IHJlbmRlcmluZyBmb3IgZWFjaCB0eXBlLlRodXMsIHlvdSBkbyBub3QgbmVlZCB0byBlZGl0IGl0IG1hbnVsbHkgd2hlbiBjaGFuZ2luZyB0aGUgZG9tYWluIG9yIG90aGVyIHNjYWxlcy4NCg0KDQpgYGB7ciBsZWdlbmQgZXhhbXBsZX0NCmxlYWZsZXQoZGF0YSA9IE1wbHNTdG9wcykgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMocHJvdmlkZXJzJENhcnRvREIuUG9zaXRyb24pICU+JQ0KICBhZGRDaXJjbGVNYXJrZXJzKGxuZyA9IH5sb25nLA0KICAgICAgICAgICAgIGxhdCA9IH5sYXQsDQogICAgICAgICAgICAgd2VpZ2h0ID0gMSwNCiAgICAgICAgICAgICBvcGFjaXR5ID0gMSwNCiAgICAgICAgICAgICBzdHJva2UgPSBUUlVFLA0KICAgICAgICAgICAgIGNvbG9yID0gfnBhbEZhY0wocHJvYmxlbSkpICU+JSANCiAgICBhZGRMZWdlbmQocG9zaXRpb24gPSAiYm90dG9tbGVmdCIsIA0KICAgICAgICAgICAgcGFsID0gcGFsRmFjTCwNCiAgICAgICAgICAgIHZhbHVlcyA9IH5wcm9ibGVtLA0KICAgICAgICAgICAgIHRpdGxlID0gIlR5cGUgb2YgU3RvcHMiKSANCmBgYA0KDQoNCiMgQ2hvcm9wbGV0aCBNYXANCg0KYGBge3J9DQpzdGF0ZXMgPC0gZ2VvanNvbmlvOjpnZW9qc29uX3JlYWQoImh0dHBzOi8vcnN0dWRpby5naXRodWIuaW8vbGVhZmxldC9qc29uL3VzLXN0YXRlcy5nZW9qc29uIiwgd2hhdCA9ICJzcCIpDQoNCmJpbnMgPC0gYygwLCAxMCwgMjAsIDUwLCAxMDAsIDIwMCwgNTAwLCAxMDAwLCBJbmYpDQpwYWwgPC0gY29sb3JCaW4oIllsT3JSZCIsIGRvbWFpbiA9IHN0YXRlcyRkZW5zaXR5LCBiaW5zID0gYmlucykNCg0KbGFiZWxzIDwtIHNwcmludGYoDQogICI8c3Ryb25nPiVzPC9zdHJvbmc+PGJyLz4lZyBwZW9wbGUgLyBtaTxzdXA+Mjwvc3VwPiIsDQogIHN0YXRlcyRuYW1lLCBzdGF0ZXMkZGVuc2l0eQ0KKSAlPiUgbGFwcGx5KGh0bWx0b29sczo6SFRNTCkNCg0KbGVhZmxldChzdGF0ZXMpICU+JQ0KICBzZXRWaWV3KC05NiwgMzcuOCwgNCkgJT4lDQogIGFkZFByb3ZpZGVyVGlsZXMoIk1hcEJveCIsIG9wdGlvbnMgPSBwcm92aWRlclRpbGVPcHRpb25zKA0KICAgIGlkID0gIm1hcGJveC5saWdodCIsDQogICAgYWNjZXNzVG9rZW4gPSBTeXMuZ2V0ZW52KCdNQVBCT1hfQUNDRVNTX1RPS0VOJykpKSAlPiUgDQogIGFkZFBvbHlnb25zKA0KICAgIGZpbGxDb2xvciA9IH5wYWwoZGVuc2l0eSksDQogICAgd2VpZ2h0ID0gMiwNCiAgICBvcGFjaXR5ID0gMSwNCiAgICBjb2xvciA9ICJ3aGl0ZSIsDQogICAgZGFzaEFycmF5ID0gIjMiLA0KICAgIGZpbGxPcGFjaXR5ID0gMC43LA0KICAgICMgaGlnaHRsaWdodCB0aGUgcG9seWdvbiB3aGVuIGN1cnNlIG92ZXIgaXQNCiAgICBoaWdobGlnaHRPcHRpb25zID0gaGlnaGxpZ2h0T3B0aW9ucygNCiAgICAgIHdlaWdodCA9IDUsDQogICAgICBjb2xvciA9ICIjNjY2IiwNCiAgICAgIGRhc2hBcnJheSA9ICIiLA0KICAgICAgZmlsbE9wYWNpdHkgPSAwLjcsDQogICAgICBicmluZ1RvRnJvbnQgPSBUUlVFKSwNCiAgICAjIGFkZCBsYWJlbHMNCiAgICBsYWJlbCA9IGxhYmVscywNCiAgICBsYWJlbE9wdGlvbnMgPSBsYWJlbE9wdGlvbnMoDQogICAgICBzdHlsZSA9IGxpc3QoImZvbnQtd2VpZ2h0IiA9ICJub3JtYWwiLCBwYWRkaW5nID0gIjNweCA4cHgiKSwNCiAgICAgIHRleHRzaXplID0gIjE1cHgiLA0KICAgICAgZGlyZWN0aW9uID0gImF1dG8iKSkNCmBgYA0K